package com.alameda.common.aop;

import com.alameda.common.base.constant.ProjectConstant;
import com.alameda.common.base.enums.EventMessageTypeEnum;
import com.alameda.common.commonpent.event.publisher.EventPublisher;
import com.alameda.common.exception.BusinessException;
import com.alameda.common.utils.*;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;

import static com.alameda.common.base.constant.MediaConstant.StringConstant.EMPTY_STRING;
import static com.alameda.common.base.constant.ProjectConstant.EVENT_MESSAGE_TYPE;
import static com.alameda.common.base.constant.ProjectConstant.OperateInfoPropertyConstant.UN_SAVE_API;

/**
 * 操作日志记录 AOP
 *
 * @author Lv
 * @date 2025/1/4
 */
@Slf4j
@Aspect
@Component
public class OperateLogAop {
    /**
     * 定义切点 扫描所有controller接口
     */
    @Pointcut("within(com.alameda.controller.v1..*Controller)")
    public void apiPoint() {
    }

    @Autowired
    private EventPublisher eventPublisher;

    /**
     * 登录接口路径集合
     */
    private static final List<String> LOGIN_API_LIST = CollectionUtils.newArrayList();

    /**
     * 实例化登录接口路径集合
     */
    static {
        LOGIN_API_LIST.add(ProjectConstant.OperateInfoPropertyConstant.ADMIN_LOGIN_API_PATH);
        LOGIN_API_LIST.add(ProjectConstant.OperateInfoPropertyConstant.APP_LOGIN_API_PATH);
    }

    /**
     * 环绕处理
     *
     * @param proceedingJoinPoint 切点对象
     * @return
     */
    @Around("apiPoint()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
        String operateResult = EMPTY_STRING;
        Object proceed = null;
        try {
            proceed = proceedingJoinPoint.proceed();
            operateResult = ProjectConstant.OperateInfoPropertyConstant.OPERATE_RESULT_SUCCESS;
            sendEvent(proceedingJoinPoint, operateResult);
        } catch (Throwable throwable) {
            operateResult = ProjectConstant.OperateInfoPropertyConstant.OPERATE_RESULT_ERROR;
            sendEvent(proceedingJoinPoint, operateResult);
            log.error("操作失败", throwable);
            BusinessException.throwException(throwable);
        }
        return proceed;
    }

    /**
     * 发送事件消息
     *
     * @param proceedingJoinPoint 切点对象
     * @param operateResult       操作结果
     */
    private void sendEvent(ProceedingJoinPoint proceedingJoinPoint, String operateResult) {
        Map<String, Object> publishEventDataMap = getPublishEventData(proceedingJoinPoint, operateResult);
        if (PubUtils.isNotNull(publishEventDataMap)) {
            // 发布事件
            eventPublisher.publish(JSONObject.toJSONString(publishEventDataMap));
        }
    }

    /**
     * 获取推送数据
     *
     * @param proceedingJoinPoint 切点对象
     * @param operateResult       操作结果
     * @return
     */
    private Map<String, Object> getPublishEventData(ProceedingJoinPoint proceedingJoinPoint, String operateResult) {
        // 获取请求接口路径
        HttpServletRequest httpServletRequest = HttpUtils.getHttpServletRequest();
        String apiPath = httpServletRequest.getRequestURI();
        log.info("请求接口路径 :{}", apiPath);

        if (UN_SAVE_API.equals(apiPath)) {
            // 无需记录接口直接跳过
            return null;
        }

        // 获取请求参数
        Object[] requestParamArr = proceedingJoinPoint.getArgs();
        Object requestParam = EMPTY_STRING;
        try {
            requestParam = requestParamArr[0];
        } catch (Exception e) {
            // do nothing
        }

        // 获取登录参数 非登录场景不做处理 防止OOM
        Object object = LOGIN_API_LIST.contains(apiPath) ? requestParam : EMPTY_STRING;
        // 获取链路id
        String traceId = LogContextUtils.getTraceId();

        // 封装发布信息内容
        Map<String, Object> map = MapUtils.newHashMap();
        map.put(ProjectConstant.OperateInfoPropertyConstant.API_NAME, apiPath);
        map.put(ProjectConstant.OperateInfoPropertyConstant.OPERATE_RESULT, operateResult);
        map.put(ProjectConstant.OperateInfoPropertyConstant.TRACE_ID, traceId);
        map.put(ProjectConstant.OperateInfoPropertyConstant.REQUEST_PARAM, object);
        map.put(EVENT_MESSAGE_TYPE, EventMessageTypeEnum.OPERATE.getTypeCode());
        return map;
    }
}
